#include "manifest_array.h"
#include "feature.h"
#include "../../semantics/method.h"
#include "../../semantics/field.h"
#include "../../semantics/error.h"
#include "../../semantics/visitordata.h"
#include "../../semantics/classsemantic.h"
#include "../../code/callfactory.h"
#include "../../code/opcodes.h"
#include "assert.h"
#include "classnode.h"

void ManifestArray::write(GraphStream * s)
{
	this->accept(s);
    fprintf(s->stream,"\tnode_%d [label=\"(%d,%d)\\nManifest array\" shape=\"rectangle\"]\n", this->number(),this->line(),this->position());
	m_compound->write(s);
	fprintf(s->stream,"\tnode_%d -> node_%d [label=\"elements\"]\n",this->number(),m_compound->number());
}

ManifestArray::~ManifestArray()
{
	if (isOwningData())
	{
		delete m_compound;
	}

	for (unsigned int i=0;i<m_delegates.size();i++)
	{
		for (unsigned int j=0;j<m_delegates[i].size();j++)
		{
			delete m_delegates[i][j];
		}
	}
}


void ManifestArray::checkCommonErrors(ParentData * data)
{
 if (m_compound==NULL)
 {
	 m_type="ARRAY[ANY]";
	 createArray(data);
	 return;
 }
 if (m_compound->vector().size()==0)
 {
	 m_type="ARRAY[ANY]";
	 createArray(data);
	 return;
 }

 getArgumentTypes(data);

 if (hasType(UNKNOWN_TYPE)==false)
 {
	 if (hasType("VOID")==false)
	 {
		 createDelegates(data);
	 }
	 else
	 {
		 m_type="ARRAY[ANY]";
		 createArray(data);
		 data->Classes->add(new VoidUsedInExpression(getNodeWithType("VOID")));
	 }
 } else m_type="ARRAY[ANY]";

}

void ManifestArray::createDelegates(ParentData * data)
{
	m_local_var=data->CurrentMethod->reserveLocalVar();

	for (unsigned int i=0;i<m_arg_types.size();i++)
	{
		m_delegates.push_back(std::vector<MethodSelectDelegate *>());
		m_delegates[i].push_back(createCloningMethod(m_arg_types[i], data->Classes));
	}

	std::vector<std::string> possible_conversions;
	ClassSemantic * sem=data->Classes->getClassByName(m_arg_types[0]);
	sem->buildHierarchy(possible_conversions,data->Classes);
	int  used_conversion=-1;

	//Scan for NCA
	for (unsigned int i=0;(i<possible_conversions.size()) && used_conversion==-1;i++)
	{
		bool canConvert=true;
		for (unsigned int j=1;j<m_arg_types.size();j++)
		{
			if (m_arg_types[j]!=possible_conversions[i])
			{
			ClassSemantic * tmpsem=data->Classes->getClassByName(m_arg_types[j]);
			bool tmpconv=tmpsem->canConvertWithoutDowncast(possible_conversions[i],data->Classes);
			canConvert=canConvert && tmpconv;
			}
		}
		if (canConvert)
			used_conversion=i;
	}

	if (used_conversion!=-1)
	{
		std::string conversion=possible_conversions[used_conversion];
		ClassSemantic * convTo=data->Classes->getClassByName(conversion);
		if (m_arg_types[0]==conversion)
			m_delegates[0].push_back(new DoNothing());
		else
			m_delegates[0].push_back(new UpcastDelegate(data->Classes->getClassByName(m_arg_types[0]),
													convTo));
		for (unsigned int i=1;i<m_arg_types.size();i++)
		{
			if (conversion==m_arg_types[i])
				m_delegates[i].push_back(new DoNothing());
			else
				m_delegates[i].push_back(selectArgumentConversion(m_compound->vector()[i],conversion,data,data->CurrentMethod));
		}
		
		m_type="ARRAY[";
		m_type+=conversion;
		m_type+="]";
		createArray(data);
	}
	else
	{
		data->Classes->add(new CommonAncestorNotFound(this));
		m_type="ARRAY[ANY]";
	}
}

void ManifestArray::getArgumentTypes(ParentData * data)
{
	m_arg_types.clear();
	m_delegates.clear();

	std::vector<AbstractNode *> & args=m_compound->vector();
	for (unsigned int i=0;i<args.size();i++)
	{
		m_arg_types.push_back(args[i]->getType(data));
	}
}

bool ManifestArray::hasType(const std::string & type)
{
	for (unsigned int i=0;i<m_arg_types.size();i++)
	{
		if (m_arg_types[i]==type)
			return true;
	}
	return false;
}

AbstractNode * ManifestArray::getNodeWithType(const std::string & type)
{
 for (unsigned int i=0;i<m_arg_types.size();i++)
 {
		if (m_arg_types[i]==type)
			return m_compound->vector()[i];
 }
 return NULL;
}
void ManifestArray::createArray(ParentData * data)
{
  ClassSemantic * date=data->Classes->getClassByName(m_type);
   if (date==NULL)
		assert(false);
}

std::string ManifestArray::getType(ParentData * data)
{
  checkCommonErrors(data);
  return m_type;
}


void ManifestArray::createInteger(JBFile * curFile, unsigned short ref)
{
	curFile->write(JVMNEW);
	curFile->write(m_integer_class);
	curFile->write(DUP);
	curFile->write(LDC_W);
	curFile->write(ref);
	curFile->write(INVOKESPECIAL);
	curFile->write(m_int_constructor);
}

void ManifestArray::createArray(JBFile * curFile)
{
	curFile->write(JVMNEW);
	curFile->write(m_array_class);
	curFile->write(DUP);
	curFile->write(INVOKESPECIAL);
	curFile->write(m_array_constructor);
	curFile->write(DUP);
	createInteger(curFile,m_size_ref);
	curFile->write(INVOKEVIRTUAL);
	curFile->write(m_array_make);

}

void ManifestArray::putObject(JBFile * curFile, unsigned int pos,ParentData * data)
{
	curFile->write(DUP);
	createInteger(curFile,m_nums_ref[pos]);
	m_compound->vector()[pos]->produceCode(data);
	for (unsigned int i=0;i<m_delegates[pos].size();i++)
		m_delegates[pos][i]->produceCode(data);
	curFile->write(INVOKEVIRTUAL);
	curFile->write(m_array_put);
}


void ManifestArray::produceCode(ParentData * data)
{
	JBFile * file=data->currentFile();
	createArray(file);
	unsigned int size=m_compound->vector().size();
	for (unsigned int i=0;i<size;i++)
	{
		putObject(file,i,data);
	}
}


void ManifestArray::fillConstant(ParentData * data)
{
	ConstantTable * table=data->currentTable();
	
	unsigned int size=m_compound->vector().size();
	m_size_ref=table->createInteger(size);

	for (unsigned int i=0;i<size;i++)
	{
		m_nums_ref.push_back( table->createInteger(i) );
		m_compound->vector()[i]->fillConstant(data);
		for (unsigned int j=0;j<m_delegates[i].size();j++)
			m_delegates[i][j]->fillConstant(data);
	}

	m_array_class=table->createArrayClass();
	m_array_constructor=table->createArrayConstructor();
	m_array_make=table->createArrayMake();
	m_array_put=table->createArrayPut();

	m_integer_class=table->createClass("INTEGER");
	m_int_constructor=table->createIntegerConstructor();

}